package models;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import tools.StringOp;

/**
 * Abstract superclass for the application models which are stored in a database.
 */
abstract public class DatabaseModel {
	
	protected static final SimpleDateFormat dbDateFormat = new SimpleDateFormat("yyyy-MM-dd");
	
	// Invalid id to indicate that the ID is not yet set
	private final static int NO_ID = -1;
	private int id = NO_ID;
	
	/**
	 * Get the id of this model. If not set, a RuntimeException is thrown.
	 * @return id
	 */
	public int getId() {
		if (this.id == NO_ID) {
			throw new RuntimeException("Id not yet set!");
		}
		return id;
	}
	
	/**
	 * Set the id of the model. Can be set only once, after that a RuntimeException is thrown.
	 * @param id
	 */
	public void setId(int id) {
		if (this.id == NO_ID) {
			this.id = id;
		}
		else {
			throw new RuntimeException("Id already set!");
		}
	}

	/**
	 * Get all data of the model as a String for use in SQL queries (comma separated).
	 * Example: "name='John', pet='dog'"
	 * @return Data String
	 */
	public String getDataString() {
		return StringOp.join(getHeaderDataPairs(), ", ");
	}	
	
	/**
	 * Get all data of the model as a String that can be used as a condition (AND separated).
	 * Example: "name='John' AND pet='dog'"
	 * @return data of the model as a String
	 */
	public String getSelectString() {
		return StringOp.join(getHeaderDataPairs(), " AND ");
	}
	
	/**
	 * Pair the headers with the data, with the data between quotes
	 * Example: [ "Name='John'", "Address='Unknown'"]
	 * @param joiner String to insert when joining parts
	 * @return List of name='value' Strings
	 */
	private List<String> getHeaderDataPairs() {
		String[] headers = getDataHeaders();
		String[] data = getData();
		List<String> pairs = new ArrayList<String>();
		for (int i = 0; i<headers.length; i++) {
			String header = headers[i];
			String field = data[i];
			pairs.add(header + "='" + field + "'");
		}
		return pairs;
	}	

	/**
	 * Get the table column names corresponding to the fields of {@link #getData()} 
	 * @return String array of column names
	 */
	public abstract String[] getDataHeaders();
	
	/**
	 * Transform the data in this model to an array of strings that can be used in SQL queries.
	 * @return data as String array
	 */
	public abstract String[] getData();
	
	/**
	 * Parse a string that contains a date in the format returned by the database (see {@link #dbDateFormat})
	 * @param date String to parse
	 * @return parsed Date
	 * @throws ParseException
	 */
	public static Date parseDbDate(String date) throws ParseException {
		return dbDateFormat.parse(date);
	}
	
	/**
	 * Return today's date (past midnight). Midnight is chosen 
	 * because parsing a date with parseDbDate also returns a date at midnight.
	 * @return Date of today at past midnight.
	 */
	public static Date getTodaysDate() {
		// Formatting will erase hour, minutes, seconds and miliseconds 
		String today = dbDateFormat.format(new Date());
		try {
			return dbDateFormat.parse(today);
		} catch (ParseException e) {
			// Will not occur off course!
			throw new RuntimeException();
		}
	}
	
	public String toString() {
		return getClass().toString() + "(id=" + id + ", " + getDataString() + ")";
	}
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + id;
		return result;
	}
	
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		DatabaseModel other = (DatabaseModel) obj;
		if (id != other.id)
			return false;
		return true;
	}
}
